---
title: 指標和引用
---

在 C++ 中，指標和引用是允許直接記憶體操作和高效資料傳遞的基本概念。它們對於理解 C++ 如何管理記憶體以及與硬體互動至關重要，並且它們與 JavaScript 的記憶體模型有顯著差異。

## 指標概念和語法

**指標**是一個儲存另一個變數記憶體位址的變數。它「指向」記憶體中儲存值的位置。指標功能強大，但需要仔細處理。

### 宣告和初始化指標

*   `int* ptr;` // 宣告一個指向整數的指標。
*   `int* ptr = &variable;` // 使用 `variable` 的位址初始化 `ptr`。

### 解引用指標

*   `*ptr` // 存取儲存在 `ptr` 中記憶體位址的值。

<UniversalEditor title="指標基礎比較" compare={true}>
```javascript !! js
// JavaScript: 沒有直接的指標，變數持有值或對物件的引用。
let num = 10;
let obj = { value: 20 };

// 在 JavaScript 中，當你賦值一個物件時，你是在複製引用。
let anotherObj = obj;
anotherObj.value = 30;
console.log(obj.value); // 30
```

```cpp !! cpp
// C++: 指標
#include <iostream>

int main() {
  int num = 10; // 一個普通的整數變數
  int* ptr = &num; // 宣告一個指標 'ptr' 並儲存 'num' 的位址

  // std::cout << "Value of num: " << num << std::endl; // 10
  // std::cout << "Address of num: " << &num << std::endl; // 記憶體位址
  // std::cout << "Value of ptr (address of num): " << ptr << std::endl; // 相同的記憶體位址
  // std::cout << "Value at address pointed by ptr: " << *ptr << std::endl; // 10 (解引用)

  *ptr = 20; // 透過指標更改 num 的值
  // std::cout << "New value of num: " << num << std::endl; // 20

  return 0;
}
```
</UniversalEditor>

## 引用概念和語法

**引用**是現有變數的別名（另一個名稱）。一旦引用被初始化為一個變數，它就不能被重新設定以引用另一個變數。引用通常用於透過引用將參數傳遞給函式。

### 宣告和初始化引用

*   `int& ref = variable;` // 宣告一個引用 `ref` 並將其綁定到 `variable`。

<UniversalEditor title="引用基礎比較" compare={true}>
```javascript !! js
// JavaScript: 物件引用在某種程度上類似於 C++ 引用。
let data = { count: 5 };
let alias = data; // 'alias' 引用與 'data' 相同的物件

alias.count = 10;
console.log(data.count); // 10

// 然而，JavaScript 引用可以被重新賦值：
alias = { count: 15 }; // 'alias' 現在引用一個新物件
console.log(data.count); // 仍然是 10
```

```cpp !! cpp
// C++: 引用
#include <iostream>

int main() {
  int value = 100; // 一個普通的整數變數
  int& ref = value; // 宣告一個引用 'ref' 並將其綁定到 'value'

  // std::cout << "Value: " << value << std::endl; // 100
  // std::cout << "Value via ref: " << ref << std::endl; // 100

  ref = 200; // 透過引用更改 'value' 的值
  // std::cout << "New value: " << value << std::endl; // 200

  // 引用不能被重新設定：
  // int anotherValue = 300;
  // ref = anotherValue; // 這會將 anotherValue 的值賦值給 value，而不是重新設定 ref

  return 0;
}
```
</UniversalEditor>

## 指標算術運算

指標算術運算涉及對指標執行算術運算。它主要用於陣列。

*   `ptr + n`：將指標向前移動 `n` 個元素（而不是 `n` 個位元組）。
*   `ptr - n`：將指標向後移動 `n` 個元素。
*   `ptr1 - ptr2`：計算 `ptr1` 和 `ptr2` 之間的元素數量。

<UniversalEditor title="指標算術運算比較" compare={true}>
```javascript !! js
// JavaScript: 陣列索引在概念上類似於指標算術運算。
let arr = [10, 20, 30, 40, 50];
console.log(arr[0]); // 10
console.log(arr[0 + 2]); // 30 (存取索引 2 的元素)
```

```cpp !! cpp
// C++: 指標算術運算
#include <iostream>

int main() {
  int arr[] = {10, 20, 30, 40, 50};
  int* ptr = arr; // ptr 指向第一個元素 (arr[0])

  // std::cout << "First element: " << *ptr << std::endl; // 10

  ptr = ptr + 2; // 將 ptr 向前移動兩個整數位置 (到 arr[2])
  // std::cout << "Element after ptr + 2: " << *ptr << std::endl; // 30

  int* ptr2 = &arr[4]; // ptr2 指向 arr[4]
  // std::cout << "Elements between ptr and ptr2: " << (ptr2 - ptr) << std::endl; // 2

  return 0;
}
```
</UniversalEditor>

## 函式指標

**函式指標**是指向函式記憶體位址的指標。它允許你間接呼叫函式、將函式作為參數傳遞或將它們儲存在資料結構中。

<UniversalEditor title="函式指標比較" compare={true}>
```javascript !! js
// JavaScript: 函式是第一類公民，可以作為參數傳遞。
function multiply(a, b) {
  return a * b;
}

function operate(func, x, y) {
  return func(x, y);
}

console.log(operate(multiply, 5, 4)); // 20
```

```cpp !! cpp
// C++: 函式指標
#include <iostream>

int multiply(int a, int b) {
  return a * b;
}

int operate(int (*funcPtr)(int, int), int x, int y) {
  return funcPtr(x, y);
}

// 在主函式中：
// int (*ptrToMultiply)(int, int) = &multiply; // 宣告並初始化函式指標
// std::cout << operate(ptrToMultiply, 5, 4) << std::endl; // 20
```
</UniversalEditor>

## 指標和引用之間的差異

| 特性           | 指標                                  | 引用                                |
| :---------------- | :--------------------------------------- | :--------------------------------------- |
| **初始化**| 可以不初始化宣告 (但危險) | 必須在宣告時初始化       |
| **空值**    | 可以是 `nullptr` (或 `NULL`)             | 不能為空                           |
| **重新賦值**  | 可以重新賦值以指向不同的變數 | 不能重新設定 (始終引用相同的變數) |
| **記憶體位址**| 儲存記憶體位址                  | 是別名；不儲存自己的位址 (概念上) |
| **解引用**   | 需要 `*` 才能解引用              | 不需要明確的解引用運算子 (直接使用) |

## 常見指標錯誤

*   **懸空指標：** 指向已釋放記憶體的指標。
*   **野指標：** 未初始化且指向任意記憶體位置的指標。
*   **空指標解引用：** 嘗試解引用 `nullptr`，導致程式崩潰。
*   **記憶體洩漏：** 忘記 `delete` 動態分配的記憶體（模組 2 中已涵蓋）。

## 與 JavaScript 引用的比較

JavaScript 沒有 C++ 意義上的明確指標或引用。相反，持有物件（包括陣列和函式）的 JavaScript 變數儲存對這些物件的**引用**。這些引用由 JavaScript 引擎的垃圾回收器管理。

*   **無手動記憶體管理：** 你不需要在 JavaScript 中 `new` 或 `delete` 物件；GC 會處理它。
*   **無指標算術運算：** 你不能對 JavaScript 引用執行算術運算。
*   **引用可以重新賦值：** 與 C++ 引用不同，JavaScript 引用可以重新賦值以指向不同的物件。

本質上，JavaScript 的物件引用在某種程度上類似於 C++ 指向物件的指標，但具有自動記憶體管理，並且無法執行指標算術運算或直接記憶體存取。

---

### 練習題：
1.  解釋 C++ 指標和 C++ 引用之間的核心差異。何時會選擇使用其中一個？
2.  編寫一個 C++ 函式，該函式接受一個整數指標作為參數，將其指向的值增加 5，然後列印新值。示範如何在 `main` 中呼叫此函式。
3.  什麼是懸空指標，以及如何在 C++ 中避免建立它？

### 專案構想：
*   使用原始指標在 C++ 中實作一個簡單的動態陣列（類似於 `std::vector`）。你的實作應包括新增元素、移除元素、調整陣列大小和按索引存取元素的函式。密切注意記憶體分配和釋放，以防止記憶體洩漏和懸空指標。
